package cn.genmer.record.config;

import cn.genmer.record.common.CommonResult;
import cn.genmer.record.common.ResultCode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.DisabledException;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;

import javax.servlet.http.HttpServletResponse;
import javax.sql.DataSource;
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.Map;

/**
 * 页面分离写法
 */
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

	private PasswordEncoder myPasswordEncoder;

	private UserDetailsService userDetailsService;

	private ObjectMapper objectMapper;

//	@Autowired
//	private CustomizeAuthenticationEntryPoint authenticationEntryPoint;

	public SecurityConfig(UserDetailsService userDetailsService, ObjectMapper objectMapper) {
		this.myPasswordEncoder =new BCryptPasswordEncoder();
		this.userDetailsService = userDetailsService;
		this.objectMapper = objectMapper;
	}

	    // 注入数据源
    @Autowired
    private DataSource dataSource;

//    配置对象
    @Bean
    public PersistentTokenRepository persistentTokenRepository(){
        JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
        jdbcTokenRepository.setDataSource(dataSource);
        return jdbcTokenRepository;
    }

	@Override
	protected void configure(HttpSecurity http) throws Exception {

		http
				.authenticationProvider(authenticationProvider())
				.httpBasic()
				//未登录时，进行json格式的提示，很喜欢这种写法，不用单独写一个又一个的类
				//异常处理(权限拒绝、登录失效等)
				.and().exceptionHandling()
				.authenticationEntryPoint((request,response,authException) -> {
					response.setContentType("application/json;charset=utf-8");
					response.setStatus(HttpServletResponse.SC_FORBIDDEN);
					PrintWriter out = response.getWriter();
//					Map<String,Object> map = new HashMap<String,Object>();
//					map.put("code",403);
//					map.put("message","未登录");
					out.write(objectMapper.writeValueAsString(CommonResult.error(ResultCode.USER_NOT_LOGIN)));
					out.flush();
					out.close();
				})
//				authenticationEntryPoint(authenticationEntryPoint) //匿名用户访问无权限资源时的异常处理
				.and()
				.authorizeRequests()
				.anyRequest().authenticated() //必须授权才能范围
				// 配置自动登陆
				.and().rememberMe().tokenRepository(persistentTokenRepository())
                .tokenValiditySeconds(1800) // 设置有效时长,单位秒
				.userDetailsService(userDetailsService)
				.and()

				.formLogin() //使用自带的登录
				.permitAll()
				//登录失败，返回json
				.failureHandler((request,response,ex) -> {
					response.setContentType("application/json;charset=utf-8");
					response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
					PrintWriter out = response.getWriter();
//					Map<String,Object> map = new HashMap<String,Object>();
//					map.put("code",401);
					if (ex instanceof UsernameNotFoundException || ex instanceof BadCredentialsException) {
						out.write(objectMapper.writeValueAsString(CommonResult.error(ResultCode.USER_USERORPASS_ERROR)));
					} else if (ex instanceof DisabledException) {
						out.write(objectMapper.writeValueAsString(CommonResult.error(ResultCode.USER_IS_FORZE)));
					} else {
						out.write(objectMapper.writeValueAsString(CommonResult.error(ResultCode.LOGON_FAILURE)));
					}
//					out.write(objectMapper.writeValueAsString(CommonResult.error(ResultCode.LOGON_FAILURE)));
					out.flush();
					out.close();
				})
				//登录成功，返回json
				.successHandler((request,response,authentication) -> {
					response.setContentType("application/json;charset=utf-8");
					PrintWriter out = response.getWriter();
					out.write(objectMapper.writeValueAsString(CommonResult.success(SecurityContextHolder.getContext().getAuthentication())));
					out.flush();
					out.close();
				})
				.and()
				.exceptionHandling()
				//没有权限，返回json
				.accessDeniedHandler((request,response,ex) -> {
					response.setContentType("application/json;charset=utf-8");
					response.setStatus(HttpServletResponse.SC_FORBIDDEN);
					PrintWriter out = response.getWriter();
					out.write(objectMapper.writeValueAsString(CommonResult.error(ResultCode.FORBIDDEN)));
					out.flush();
					out.close();
				})
				.and()
				.logout()
				//退出成功，返回json
				.logoutSuccessHandler((request,response,authentication) -> {
					response.setContentType("application/json;charset=utf-8");
					PrintWriter out = response.getWriter();
					out.write(objectMapper.writeValueAsString(CommonResult.success("登出成功")));
					out.flush();
					out.close();
				})
				.permitAll();
		//开启跨域访问
		http.cors().disable();
		//开启模拟请求，比如API POST测试工具的测试，不开启时，API POST为报403错误
		http.csrf().disable();
	}

	@Override
	public void configure(WebSecurity web) {
		//对于在header里面增加token等类似情况，放行所有OPTIONS请求。
		web.ignoring().antMatchers(HttpMethod.OPTIONS, "/**");
	}

	@Bean
	public AuthenticationProvider authenticationProvider() {
		DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
		//对默认的UserDetailsService进行覆盖
		authenticationProvider.setUserDetailsService(userDetailsService);
		authenticationProvider.setPasswordEncoder(myPasswordEncoder);
		return authenticationProvider;
	}

}
